#
# A parser for OBJ files.
#
# Auther: Wesley Liu
# Email: jinvy.liu@gmail.com
# Date: 12/21/2007
#

require File.dirname(__FILE__)+'/IFileParser'
require File.dirname(__FILE__)+'/../Scene/ListScene'
require File.dirname(__FILE__)+'/../Scene/Mesh'
require File.dirname(__FILE__)+'/../Scene/Material'

class OBJParser < IFileParser
    def initialize
    end
    
    def for
        'obj'
    end
    
    def parse filename, progressTarget
        @fileDir = File.dirname filename
        scene = ListScene.new
        v = Array.new
        vt = Array.new
        vn = Array.new
        v3 = Array.new
        t3 = Array.new
        n3 = Array.new
        fs = Array.new
        meshStack = Array.new
        filesize = File.size(filename).to_f
        increase = 0.0
        File.open(filename).each do |line|
            # Update the progress bar
            increase += (line.size / filesize * 100)
            if increase > 1
                progressTarget.value += increase.to_i
                increase -= increase.to_i
            end
            # Parse the file data
            if /v\s+(\S+)\s+(\S+)\s+(\S+)/ =~ line
                v.push Vector[$1.to_f, $2.to_f, $3.to_f]
                next
            end
            if /vt\s+(\S+)\s+(\S+)/ =~ line
                vt.push Vector[$1.to_f, $2.to_f]
                next
            end
            if /vn\s+(\S+)\s+(\S+)\s+(\S+)/ =~ line
                vn.push Vector[$1.to_f, $2.to_f, $3.to_f]
                next
            end
            if /g\s+(\S+)/ =~ line
                # g means a new group
                # thus all the data for the last mesh has been handled
                addMesh2Scene scene, meshStack, v
                meshStack.push Mesh.new
                meshStack.last.name = $1
                next
            end
            if /f\s+(.+)/ =~ line
                fs.clear
                $1.split(" ").each do |vs|
                    fs.push(Vx.new(vs))
                end
                for i in 1..(fs.length-2) do
                    v3[0] = fs[0].vi - 1
                    v3[1] = fs[i].vi - 1
                    v3[2] = fs[i+1].vi - 1
                    meshStack.last.faces.push Vector.elements(v3, true)
                    if fs[0].vt != nil
                        t3[0] = fs[0].vt - 1
                        t3[1] = fs[i].vt - 1
                        t3[2] = fs[i+1].vt - 1
                        3.times do |ii|
                            if meshStack.last.texturexys[v3[ii]] == nil
                                meshStack.last.texturexys[v3[ii]] = vt[t3[ii]]
                            end
                        end
                    end
                    if fs[0].vn != nil
                        n3[0] = fs[0].vn - 1
                        n3[1] = fs[i].vn - 1
                        n3[2] = fs[i+1].vn - 1
                        3.times do |ii|
                            if meshStack.last.normals[v3[ii]] == nil
                                meshStack.last.normals[v3[ii]] = vn[n3[ii]].clone
                            else
                                meshStack.last.normals[v3[ii]] += vn[n3[ii]]
                            end
                        end
                    end
                end
                next
            end
            if /mtllib\s+(\S+)/ =~ line
                parseMtl $1
                next
            end
            if /usemtl\s(\S+)/ =~ line
                meshStack.last.material = @materials[$1]
                next
            end
        end
        addMesh2Scene scene, meshStack, v
        scene
    end
    
    def addMesh2Scene scene, meshStack, allVtx
        while meshStack.last != nil
            mesh = meshStack.pop
            if mesh.faces != nil && mesh.faces.length > 0
                vimin = vimax = mesh.faces[0][0]
                mesh.faces.each do |f|
                    3.times do |i|
                        if f[i] < vimin
                            vimin = f[i]
                        elsif f[i] > vimax
                            vimax = f[i]
                        end
                    end
                end
                mesh.vertices = allVtx[vimin..vimax]
                mesh.normals = mesh.normals[vimin..vimax]
                mesh.texturexys = mesh.texturexys[vimin..vimax]
                vtrmin = Vector[vimin, vimin, vimin]
                mesh.faces.length.times do |i|
                    mesh.faces[i] -= vtrmin
                end
                mesh.normalize
                scene.addmesh mesh
            end
        end
    end
    
    def parseMtl mtlFile
        @materials = Hash.new
        name = ''
        File.open(@fileDir+'/'+mtlFile).each do |line|
            if /newmtl\s+(\S+)/ =~ line
                name = $1
                @materials[name] = ObjMaterial.new(name)
            else
                @materials[name].parse(line) if @materials[name] != nil
            end
        end
    end
end

# for vi/vt/vn
class Vx
    attr_reader :vi, :vt, :vn
    def initialize vx
        x = vx.split("/")
        @vi = x[0].to_i
        @vt = x[1].to_i
        @vn = x[2].to_i
    end
end

# for material
class ObjMaterial < Material
    def initialize nm
        @name = nm
    end
    def parse line
        if /Ka\s+(\S+)\s+(\S+)\s+(\S+)/ =~ line
            @ambient = [$1.to_f, $2.to_f, $3.to_f]
        elsif /Kd\s+(\S+)\s+(\S+)\s+(\S+)/ =~ line
            @diffuse = [$1.to_f, $2.to_f, $3.to_f]
        elsif /Ks\s+(\S+)\s+(\S+)\s+(\S+)/ =~ line
            @specular = [$1.to_f, $2.to_f, $3.to_f]
        elsif /d\s+(\S+)/ =~ line
            @ambient[3] = $1.to_f if @ambient != nil
            @diffuse[3] = $1.to_f if @diffuse != nil
            @specular[3] = $1.to_f if @specular != nil
        elsif /map_Kd\s+(\S+)/ =~ line
            @textureFile = $1
        elsif /Ns\s+(\S+)/ =~ line
            @shininess = $1.to_f
        end
    end
end

if File.basename(__FILE__) == File.basename($0)
    require 'fox16'
    include Fox
    
    beginImport = Time.now
    scene = OBJParser.new.parse(File.dirname(__FILE__)+'/../../test/plane.obj', FXDataTarget.new(0))
    importTime = Time.now - beginImport
    puts 'import time: ' + importTime.to_s
    scene.each do |mesh|
        puts mesh.name
        puts mesh.vertices.length
        puts mesh.faces.length
        puts mesh.normals.length
        #puts 'begin compute normals'
        #t = Time.now
        puts mesh.getNormals.length
        #puts 'compute normal time: ' + (Time.now - t).to_s
        #puts 'begin verticesPack'
        #t = Time.now
        puts mesh.verticesPack.length
        #puts 'verticesPack time: ' + (Time.now - t).to_s
        puts mesh.texturexysPack.length
    end
end